#include <stx/imp/cmn.hpp>

inline namespace stx
{
void Stopwatch::reset() noexcept
{
    this->start_tp_ = SteadyClock::now();
}

void sleepForEver() noexcept
{
    while (true)
    {
        sleepFor(24h);
    }
}

std::size_t DateTime::getHashCode() const noexcept
{
    auto n = 0uz;
    std::memcpy(&n, this, std::min(sizeof(n), sizeof(*this)));
    return n;
}

DateTime& DateTime::setYear(std::size_t const year) noexcept
{
    this->year_ = year;
    return *this;
}

DateTime& DateTime::setMonth(std::size_t const month) noexcept
{
    this->month_ = month;
    return *this;
}

DateTime& DateTime::setDay(std::size_t const day) noexcept
{
    this->day_ = day;
    return *this;
}

DateTime& DateTime::setHour(std::size_t const hour) noexcept
{
    this->hour_ = hour;
    return *this;
}

DateTime& DateTime::setMinute(std::size_t const minute) noexcept
{
    this->minute_ = minute;
    return *this;
}

DateTime& DateTime::setSecond(std::size_t const second) noexcept
{
    this->second_ = second;
    return *this;
}

DateTime& DateTime::setMillisecond(std::size_t const millisecond) noexcept
{
    this->millisecond_ = millisecond;
    return *this;
}

DateTime& DateTime::setWeekday(std::size_t const weekday) noexcept
{
    this->weekday_ = weekday;
    return *this;
}

std::size_t DateTime::getYear() const noexcept
{
    return this->year_;
}

std::size_t DateTime::getMonth() const noexcept
{
    return this->month_;
}

std::size_t DateTime::getDay() const noexcept
{
    return this->day_;
}

std::size_t DateTime::getHour() const noexcept
{
    return this->hour_;
}

std::size_t DateTime::getMinute() const noexcept
{
    return this->minute_;
}

std::size_t DateTime::getSecond() const noexcept
{
    return this->second_;
}

std::size_t DateTime::getMillisecond() const noexcept
{
    return this->millisecond_;
}

std::size_t DateTime::getWeekday() const noexcept
{
    return this->weekday_;
}

SysTimePoint FileToSys(FileTimePoint const tp_file)
{
    return FileClock::to_sys(
        std::chrono::time_point_cast<typename SysTimePoint::duration>(tp_file));
}

FileTimePoint SysToFile(SysTimePoint const tp_sys)
{
    return FileClock::from_sys(tp_sys);
}

static_assert(sizeof(DateTime) == 7);

DateTime getDateTime(SysTimePoint const tp_sys)
{
    auto const tt   = SystemClock::to_time_t(tp_sys);
    auto const p_tm = std::gmtime(&tt);

    return DateTime{}
        .setYear(Number(p_tm->tm_year + 1900))
        .setMonth(Number(p_tm->tm_mon + 1))
        .setDay(Number(p_tm->tm_mday))
        .setHour(Number(p_tm->tm_hour))
        .setMinute(Number(p_tm->tm_min))
        .setSecond(Number(p_tm->tm_sec))
        .setMillisecond(
            Number(to<Milliseconds>(tp_sys.time_since_epoch()).count() % 1000))
        .setWeekday(Number(p_tm->tm_wday));
}

DateTime getDateTime()
{
    return getDateTime(SystemClock::now());
}

std::string getDateTimeText(DateTimeFmt const fmt, SysTimePoint const tp_sys)
{
    using enum DateTimeFmt;

    if (K_TIMESTAMP == fmt)
    {
        return tos(getTimestamp<Seconds>(tp_sys));
    }

    if (K_TIMESTAMP_MS == fmt)
    {
        return tos(getTimestamp<Milliseconds>(tp_sys));
    }

    auto const dt = getDateTime(tp_sys);

    auto buf_year = std::array<char, 24>{'0', '0', '0', '0'};
    auto buf_mon  = std::array<char, 24>{'0', '0'};
    auto buf_day  = std::array<char, 24>{'0', '0'};
    auto buf_hour = std::array<char, 24>{'0', '0'};
    auto buf_min  = std::array<char, 24>{'0', '0'};
    auto buf_sec  = std::array<char, 24>{'0', '0'};
    auto buf_ms   = std::array<char, 24>{'0', '0', '0'};

    auto sv_year = std::string_view{};
    auto sv_mon  = std::string_view{};
    auto sv_day  = std::string_view{};
    auto sv_hour = std::string_view{};
    auto sv_min  = std::string_view{};
    auto sv_sec  = std::string_view{};
    auto sv_ms   = std::string_view{};

    auto const fn = [](std::uint64_t const   value,
                       std::array<char, 24>& buf,
                       std::size_t const     fixed_width,
                       std::string_view&     sv)
    {
        auto const p_start = buf.data() + fixed_width;
        auto const [p_end, ec] =
            std::to_chars(p_start, buf.data() + buf.size(), value);
        if (std::errc{} != ec)
        {
            return false;
        }

        auto size = Number(p_end - p_start);
        if (size >= fixed_width)
        {
            size = fixed_width;
        }

        std::copy(p_start, p_start + size, buf.data() + fixed_width - size);
        sv = tosv(buf.data(), fixed_width);

        return true;
    };

    if (fn(dt.getYear(), buf_year, 4, sv_year) &&
        fn(dt.getMonth(), buf_mon, 2, sv_mon) &&
        fn(dt.getDay(), buf_day, 2, sv_day) &&
        fn(dt.getHour(), buf_hour, 2, sv_hour) &&
        fn(dt.getMinute(), buf_min, 2, sv_min) &&
        fn(dt.getSecond(), buf_sec, 2, sv_sec))
    {
        if (K_COMMON == fmt)
        {
            return scat(sv_year,
                        "-"sv,
                        sv_mon,
                        "-"sv,
                        sv_day,
                        " "sv,
                        sv_hour,
                        ":"sv,
                        sv_min,
                        ":"sv,
                        sv_sec);
        }

        if (K_COMMON_COMPACT == fmt)
        {
            return scat(sv_year, sv_mon, sv_day, sv_hour, sv_min, sv_sec);
        }

        if (K_ISO == fmt)
        {
            return scat(sv_year,
                        "-"sv,
                        sv_mon,
                        "-"sv,
                        sv_day,
                        "T"sv,
                        sv_hour,
                        ":"sv,
                        sv_min,
                        ":"sv,
                        sv_sec,
                        "Z"sv);
        }

        if (!fn(dt.getMillisecond(), buf_ms, 3, sv_ms))
        {
            return {};
        }

        if (K_COMMON_MS == fmt)
        {
            return scat(sv_year,
                        "-"sv,
                        sv_mon,
                        "-"sv,
                        sv_day,
                        " "sv,
                        sv_hour,
                        ":"sv,
                        sv_min,
                        ":"sv,
                        sv_sec,
                        "."sv,
                        sv_ms);
        }

        if (K_COMMON_COMPACT_MS == fmt)
        {
            return scat(
                sv_year, sv_mon, sv_day, sv_hour, sv_min, sv_sec, sv_ms);
        }

        if (K_ISO_MS == fmt)
        {
            return scat(sv_year,
                        "-"sv,
                        sv_mon,
                        "-"sv,
                        sv_day,
                        "T"sv,
                        sv_hour,
                        ":"sv,
                        sv_min,
                        ":"sv,
                        sv_sec,
                        "."sv,
                        sv_ms,
                        "Z"sv);
        }
    }

    RUNTIME_ERROR("Failed to getDateTimeText: "sv, fmt);
}

std::string getDateTimeText(DateTimeFmt const fmt)
{
    return getDateTimeText(fmt, SystemClock::now());
}
} // namespace stx